﻿using System;
using System.Threading;

namespace MDK.Device.Components
{
    public interface IUncap : IPlcDeviceComponent
    {
        void Reset();
        void SwitchToContinuous();
        void SwitchToStepMode();
        void Start();
        void Stop();
        bool WaitCompleted(int timeout = int.MaxValue);
        bool CanDropIn();
        bool CanDragOut();
        void EnqueueOne(Tube tube);
        Tube DequeueOne();
        bool HasUncapFail();
        int HoleCount { get; }
        int DialTubeTable { get; }
        int DragOutHole { get; }
        bool IsFull { get;}
        bool IsEmpty { get; }
    }

    public class Uncap : IUncap
    {
        private PlcAddress _reset;
        private PlcAddress _mode;
        private PlcAddress _start; 
        private PlcAddress _stop;
        private PlcAddress _canIn;
        private PlcAddress _fail;

        private Tube[] tubes;
        private int _queueIndex;

        public IPlcDevice Device { get; }

        public int DialTubeTable { get; private set; }

        public bool IsEmpty
        {
            get { return DialTubeTable == 0; }
        }

        private TimeSpan _stepInterval = TimeSpan.FromSeconds(2);
        private DateTime _lastStartTime = DateTime.MinValue;

        public Uncap(IPlcDevice device, PlcAddress reset, PlcAddress mode, PlcAddress start, PlcAddress stop, PlcAddress canIn, PlcAddress fail)
        {
            Device = device;

            _reset = reset;
            _mode = mode;
            _start = start;
            _stop = stop;
            _canIn = canIn;
            _fail = fail;

            tubes = new Tube[HoleCount];
            _queueIndex = 0;
        }

        public void Reset()
        {
            Device.PLC.ResetBit(_start);
            Device.PLC.SetBit(_reset);
            Device.PLC.SetBit(_start);
            Thread.Sleep(2000);
            Device.PLC.ResetBit(_reset);

            DialTubeTable = 0;
        }

        public void SwitchToContinuous()
        {
            Device.PLC.SetBit(_mode);
        }

        public void SwitchToStepMode()
        {
            Device.PLC.ResetBit(_mode);
        }

        public void Start()
        {
            while (DateTime.Now - _lastStartTime < _stepInterval)
            {
                Thread.Sleep(100);
            }
            Device.PLC.ResetBit(_start);
            Device.PLC.ResetBit(_stop);
            Device.PLC.SetBit(_start);
            _lastStartTime = DateTime.Now;
            DialTubeTable <<= 1;
            if((DialTubeTable & (1 << HoleCount)) != 0)
            {
                DialTubeTable |= 1;
            }
            DialTubeTable &= ((1 << HoleCount) - 1);
            _queueIndex++;
            _queueIndex %= HoleCount;
        }

        public void Stop()
        {
            Device.PLC.ResetBit(_start);
            Device.PLC.SetBit(_stop);
        }

        public bool WaitCompleted(int timeout = Int32.MaxValue)
        {
            return true;
        }

        public bool CanDropIn()
        {
            if (!Device.PLC.BitIsSet(_canIn) && (DialTubeTable & 1) == 0)
            {
                return true;
            }
            while (DateTime.Now - _lastStartTime < _stepInterval && Device.PLC.BitIsSet(_canIn) && (DialTubeTable & 1) == 0)
            {
                Thread.Sleep(20);
            }
            return !Device.PLC.BitIsSet(_canIn) && (DialTubeTable & 1) == 0;
          
        }

        public bool CanDragOut()
        {
            return !Device.PLC.BitIsSet(_fail) && (DialTubeTable & (1 << DragOutHole)) != 0;
        }

        public void EnqueueOne(Tube tube)
        {
            DialTubeTable |= 1;
            tubes[_queueIndex] = tube;
        }
        public Tube DequeueOne()
        {
            DialTubeTable &= ~(1 << DragOutHole);
            var outIndex = (_queueIndex + HoleCount - DragOutHole) % HoleCount;
            var tube = tubes[outIndex];
            tubes[outIndex] = null;
            return tube;
        }

        public bool HasUncapFail()
        {
            return Device.PLC.BitIsSet(_fail);
        }

        public int HoleCount { get; } = 12;
        
        public int DragOutHole { get; } = 8;

        public bool IsFull
        {
            get { return (1 << HoleCount) - 1 == DialTubeTable; }
        }
    }
}